﻿//===============================================================================
// Magic OQL
//===============================================================================
// Copyright ?Magic Thought Corporation. All rights reserved.
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY
// OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT
// LIMITED TO THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
// FITNESS FOR A PARTICULAR PURPOSE.
//===============================================================================
// Richie (http://www.cnblogs.com/RicCC)
// March 7, 2008
//===============================================================================

using System.Collections.Generic;
using System.Text;
using Antlr.Runtime;
using Antlr.Runtime.Tree;

namespace Magic.Framework.OQL.Expressions
{
    /// <summary>
    /// <para>1. Ignore the imaginary token text.</para>
    /// <para>2. Ignore some keyword tokens and append their text to the root.</para>
    /// <para>3. Other subtree tokens follow the normal way.</para>
    /// </summary>
    /// <remarks>
    /// The differences between CombinedKeywords and Text is that CombinedKeywords may have
    /// subtree expressions, but none for Text.
    /// </remarks>
    public class CombinedKeywords : Expression
    {
        private StringBuilder _texts = new StringBuilder();
        private string _tokenText = string.Empty;
        private int[] _combinedTypes;
        private bool _combineAll = false;
        private int[] _skipTypes;
        private bool _skipAll = false;
        private int _count = 0;

        public CombinedKeywords(CombinedKeywords ck)
            :this(ck.token, ck._tokenText, ck._skipAll, ck._skipTypes, ck._combineAll, ck._combinedTypes)
        {
        }
        public CombinedKeywords(IToken t, string tokenText, bool skipAll, int[] skipType, bool combineAll, int[] combineTypes)
            : base(t)
        {
            this.SetupFilters(tokenText, skipAll, skipType, combineAll, combineTypes);
            this._texts.Append(tokenText);
            this.Token.Text = tokenText;
        }

        private void SetupFilters(string tokenText, bool skipAll, int[] skip, bool combineAll, int[] combine)
        {
            this._tokenText = tokenText;
            this._skipAll = skipAll;
            this._skipTypes = skip;
            this._combineAll = combineAll;
            this._combinedTypes = combine;
        }
        private bool IsSkipType(Expression exp)
        {
            if (this._skipAll || exp == null) return true;
            return !exp.IsNil && this.ContainSkipType(exp.Token.Type);
        }
        private bool ContainSkipType(int type)
        {
            for (int i = 0; this._skipTypes != null && i < this._skipTypes.Length; i++)
                if (this._skipTypes[i] == type) return true;
            return false;
        }
        private void SkipHandler()
        {
            if (!this.IsSkipType(this) && !this.IsNil) this._texts.Append(this.Token.Text);
            this._count++;
        }
        private bool ContainCombineType(int type)
        {
            for (int i = 0; this._combinedTypes != null && i < this._combinedTypes.Length; i++)
                if (this._combinedTypes[i] == type) return true;
            return false;
        }
        private bool IsCombineType(Expression exp)
        {
            if (exp == null) return false;
            if (this._combineAll) return true;
            return !exp.IsNil && this.ContainCombineType(exp.Token.Type);
        }
        /// <summary>
        /// Give derived classes a chance to do something or make a decision before combining tokens, 
        /// such as adding a white space char between multiple keywords.
        /// </summary>
        /// <param name="count">The count of existing tokens in this expression.</param>
        /// <param name="builder">StringBuilder for the text of this expression.</param>
        /// <param name="exp">The expression to be combined.</param>
        protected virtual bool PreCombine(int count, StringBuilder builder, IToken token)
        {
            return true;
        }
        private void CombineHandler(IToken token)
        {
            if (token == null || token.Text.Length <= 0) return;
            if (!this.PreCombine(this._count, this._texts, token)) return;
            this._texts.Append(token.Text);
            this._count++;
        }

        public override void AddChild(Expression t)
        {
            if (this.IsSkipType(t)) return;

            if (this.IsCombineType(t))
            {
                this.CombineHandler(t.Token);
                for (int i = 0; t._children != null && i < t._children.Count; i++)
                    this.CombineHandler(t._children[i].Token);
            }
            else this.InternalAddChild(t);
        }
        public override Expression DupNode()
        {
            return new CombinedKeywords(this.Token, this._tokenText, this._skipAll, this._skipTypes, this._combineAll, this._combinedTypes);
        }

        public override string ToString()
        {
            return this._texts.ToString();
        }
    }
}